#ifndef WORLD_H
#define WORLD_H

#include <random>

#include <vector>
#include "Objects.h"
#include "RGBColor.h"
#include "Tracer.h"
#include "ViewPlane.h"
#include "PaintArea.h"
#include "DepthTracer.h"
#include "Image.h"
#include "GeometricObjects.h"
#include "SceneFactory.h"
#include "DepthTracer.h"
#include "ShadowRay.h"
#include "Light.h"
#include "Reflective.h"
#include "IrradianceCache.h"
#include "ShadowTester.h"
#include <iostream>
#include <stdio.h>
#include "gw.h"
#include "Logger.h"
#include "Camera.h"

#define UPDATE_COUNT 2000
//#define APPLY_SHADOWS
#define DEPTH_LIMIT 5

#define INDIRECT_ONLY

#define max(A,B) ((A > B)? (A ): (B))
namespace Engine{

	class World
	{
	public:

		const float VIEW_PLANE_EYE_DISTANCE = 1;

		ViewPlane vp;
		RGBColor backgroundColor;
		Sphere sphere;
		Tracer* tracer_ptr;
		PaintArea *paintArea;
		Pixel *buffer;
		Scene *currentScene;
		int sceneType;
		int eye;
		IrradianceCache* integrator;
		ShadowTester m_shadowTester;
		Image *currentImage;
		Camera* m_camera;

		//d is like zooming into the scene
		int d;
		int m_currentY;
		Pixel* m_currentPixel;

		Logger m_logFile;

		World(void) : tracer_ptr(NULL)
		{
			backgroundColor = RGBColor(0, 0, 0);
		}

		World(int& m_sceneType) : sceneType(m_sceneType)
		{
		}

		World(int m_sceneType, int m_width, int m_height) : sceneType(m_sceneType)
		{
			vp.hres = m_width;
			vp.vres = m_height;
			vp.s = 1;
			currentImage = new Image(600, 600);
		}


		World(int& m_sceneType, int m_width, int m_height) : sceneType(m_sceneType)
		{
			vp.hres = m_width;
			vp.vres = m_height;
			vp.s = 1;
			currentImage = new Image(600, 600);
			
		}


		void build(void)
		{
			int numSamples = 25;
			vp.gamma = 1.0;
			vp.s = 1.0;
			//vp.SetSampler(new Jittered(numSamples));
			eye = 500;
			buffer = new Pixel[vp.hres * vp.vres];

			int size = vp.hres * vp.vres;

			for (int index = 0; index  < size; index ++) 
			{
				buffer[index] = 0;
			}

			backgroundColor = RGBColor(0, 0, 0);
			clock_t startingTimer = clock();
			currentScene = SceneFactory::GetScene(sceneType);
			tracer_ptr = new DepthTracer(currentScene,true);
			clock_t endingTimer = clock();
			double timeTaken = (((double)endingTimer - startingTimer) / (double)CLOCKS_PER_SEC);
			integrator = new IrradianceCache(currentScene,tracer_ptr);
			tracer_ptr->UseAccelerationStructure();
			m_shadowTester = ShadowTester(tracer_ptr, currentScene->m_sceneLight);
			
			//focusing on the yellow ball at the moment
			m_camera = new  Camera(Point3D(0, 0, VIEW_PLANE_EYE_DISTANCE), Point3D(0, -3, -15));

			//progressive rendering
			m_currentPixel = buffer;
			m_currentY = 0;

			
		}

		void build(Camera* p_camera)
		{
			int numSamples = 25;
			vp.gamma = 1.0;
			vp.s = 1.0;
			//vp.SetSampler(new Jittered(numSamples));
			eye = 500;
			buffer = new Pixel[vp.hres * vp.vres];

			int size = vp.hres * vp.vres;

			for (int index = 0; index < size; index++)
			{
				buffer[index] = 0;
			}

			backgroundColor = RGBColor(0, 0, 0);
			clock_t startingTimer = clock();
			currentScene = SceneFactory::GetScene(sceneType);
			tracer_ptr = new DepthTracer(currentScene, true);
			clock_t endingTimer = clock();
			double timeTaken = (((double)endingTimer - startingTimer) / (double)CLOCKS_PER_SEC);
			integrator = new IrradianceCache(currentScene, tracer_ptr);
			tracer_ptr->UseAccelerationStructure();
			m_shadowTester = ShadowTester(tracer_ptr, currentScene->m_sceneLight);

			//focusing on the yellow ball at the moment
			m_camera = p_camera;

			//progressive rendering
			m_currentPixel = buffer;
			m_currentY = 0;


		}


		void open_window(const int hres, const int vert_res)
		{}

		///samples in a simple way.
		///
		void RenderViaSamples()
		{
			RGBColor pixelColor;
			Ray ray;
			double zw = 100.0;
			double xx, yy;
			Point2D sp;
			Point2D pp;

			//open_window(vp.hres, vp.vres);
			ray.o = Point3D(0, 0, -10);

			for (int r = 0; r < vp.vres; r++)
			{
				for (int c = 0; c <= vp.hres; c++)
				{
					pixelColor = RGBColor::GetBlack();

					//each pixel will have an x amount of samples which iwll be initialized at the 
					//start of the the world  class intiialization 
					for (int j = 0; j < vp.hres; c++)
					{
						//getting the next sample
						sp = vp.sampler->SampleUnitSquare();

						pp.x = vp.s * (c - 0.5 * vp.hres + sp.x);
						pp.y = vp.s * (r - 0.5 * vp.vres + sp.y);

						ray.d = Point3D(pp.x, pp.y, zw);

						ShadeRec sr;
						pixelColor = pixelColor + tracer_ptr->TraceRay(ray.o, ray.d, sr);
					}

					pixelColor = pixelColor / vp.numSamples;

					display_pixels(r, c, pixelColor);
				}
			}
		}

		RGBColor GetShade(ShadeRec& sr, int depth) 
		{
			GeometricObjects* object = sr.object;
			Material *materialPtr;
			
			if (sr.m_hitTriangle == NULL)
			{
				materialPtr = sr.object->GetMaterial();
			}
			else 
			{
				materialPtr = sr.m_hitTriangle->GetMaterial();
			}

			if (materialPtr == NULL)
			{
				cout << "penis" << endl;
			}

			RGBColor outputColor = RGBColor(0);
			#ifdef APPLY_SHADOWS
				if (m_shadowTester.TestForShadow(sr.ray, sr))
				{
					return RGBColor(0);
				}
			#endif

			switch (materialPtr->GetType())
			{
			case GLOSSY:
			case DIFF:
				{
					#ifdef INDIRECT_ONLY
						 //check shadow ray 
						 //RGBColor indirectColor = integrator->GetIndirectLightingAtPoint(sr);
						 outputColor = outputColor + materialPtr->Shade(sr, currentScene->m_sceneLight, tracer_ptr);// +indirectColor*materialPtr->Getcolor();
						 //color can never be full red for example
					/*	 if (indirectColor == RGBColor(1,0,0))
						 {
							 outputColor = indirectColor;
						 }
						 else
						 {
							 outputColor = indirectColor * materialPtr->Getcolor();
						 }*/
						 
						 //m_logFile.WriteToFile(outputColor.ToString());
						 return outputColor;
					#else
						 //check shadow ray 
						 RGBColor indirectColor = integrator->GetIndirectLightingAtPoint(sr);
						 outputColor = outputColor + materialPtr->Shade(sr, currentScene->m_sceneLight, tracer_ptr) + indirectColor*materialPtr->Getcolor();
						 //outputColor = indirectColor * materialPtr->Getcolor();
						 //m_logFile.WriteToFile(outputColor.ToString());
						 return outputColor;
					#endif
					
				}
				break;

			case REFL:
				{
					ShadeRec refDetails;
					Point3D N = sr.object->GetNormal(sr.localHitPoint);
					Point3D r = sr.ray.d - 2 * sr.ray.d.dot(N) * N;
					float bias = 1e-4;
					Point3D newOrigin = (sr.localHitPoint + bias * r);
					Ray newRay(newOrigin, r);
					newRay.d.normalize();
					//solve this problem
					if (++depth < DEPTH_LIMIT)
					{
						tracer_ptr->TraceRay(newRay, refDetails);
						GeometricObjects *currentObject = refDetails.object;
						if (currentObject != NULL)
						{
							//outputColor += materialPtr->Shade(sr, currentScene->m_sceneLight) * GetShade(refDetails, depth);
							RGBColor rcol = GetShade(refDetails, depth);
							outputColor = outputColor + materialPtr->Getcolor() * rcol * materialPtr->GetReflectionConstant();
						}
						else
						{
							outputColor += materialPtr->Shade(sr, currentScene->m_sceneLight, tracer_ptr);
						}
					}
					return outputColor;
				}
				break;
			default:
				return outputColor;
				break;
			}
			
		}

		bool render_scene_non_progressive(void)
		{
			vector<GeometricObjects*> objects;

			RGBColor pixelColor;
			Ray ray;
			//open_window();
			tracer_ptr->UseAccelerationStructure();
			ray.d = Point3D(0, 0, -4);
			Pixel *currentPixel = m_currentPixel;
			//currentPixel += vp.vres * vp.hres - 1;
			float zw = 10;
			int count = 0;
			clock_t startingTime = clock();

			float invWidth = 1 / float(vp.hres), invHeight = 1 / float(vp.vres);
			float fov = 30, aspectratio = vp.hres / float(vp.vres);
			float angle = tan(PI_NUM * 0.5 * fov / 180.);
			for (unsigned y = 0; y < vp.vres; ++y) {
				for (unsigned x = 0; x < vp.hres; ++x)
				{
					float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
					float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
					Point3D raydir(xx, yy, -1);

					Point3D tempDir = m_camera->GetCameraDirection(raydir);

					raydir.normalize();
					pixelColor = RGBColor(0);
					ShadeRec rec;
					if (tracer_ptr->TraceRay(Ray(m_camera->eye, tempDir), rec))
					{
						
						int depth = 0;
						//pixelColor = GetShade(rec, depth);
						float tempTmin = min(rec.tmin, 1);
						pixelColor = RGBColor(rec.tmin);
					}
					*currentPixel = pixelColor.toInt();

					++currentPixel;
					++count;
				}
			}

			clock_t endTimer = clock();


			double timeInSeconds = ((double)endTimer - startingTime) / (double)CLOCKS_PER_SEC;
			return true;
		}

		bool render_scene()
		{
			vector<GeometricObjects*> objects;

			RGBColor pixelColor;
			Ray ray;
			//open_window();
			tracer_ptr->UseAccelerationStructure();
			ray.d = Point3D(0, 0, -4);
			Pixel *currentPixel = m_currentPixel;
			//currentPixel += vp.vres * vp.hres - 1;
			float zw = 10;
			int count = 0;
			clock_t startingTime = clock();

			float invWidth = 1 / float(vp.hres), invHeight = 1 / float(vp.vres);
			float fov = m_camera->m_fov, aspectratio = m_camera->m_aspectRatio;
			//float fov = 30, aspectratio = vp.hres / float(vp.vres);
			float eyeViewPlaneDistance = m_camera->eye.x + m_camera->m_viewPlaneDistance;
			float angle = tan(PI_NUM * 0.5 * fov / 180.);
			for (unsigned y = m_currentY; y < vp.vres; ++y) {
				for (unsigned x = 0; x < vp.hres; ++x) {
						float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
						float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
						Point3D raydir(xx, yy, -1);
						raydir.normalize();

						Point3D tempDir = m_camera->GetCameraDirection(raydir);

						pixelColor = RGBColor(0);
						ShadeRec rec;
						if (tracer_ptr->TraceRay(Ray(m_camera->eye, tempDir), rec))
						{
							int depth = 0;
							pixelColor = GetShade(rec, depth);
						}
						//*currentPixel = pixelColor.toInt();
						pixelColor.Convert();
						SetColor(x, y, pixelColor.r, pixelColor.g, pixelColor.b);
						++currentPixel;
						++count;
						m_currentShadrec = rec;
				}

				if (count >= UPDATE_COUNT)
				{
					m_currentY = ++y;
					m_currentPixel = currentPixel;
					return false;
				}
			}

			clock_t endTimer = clock();


			double timeInSeconds = ((double)endTimer - startingTime) / (double)CLOCKS_PER_SEC;
			return true;
		}

		bool render_scene_zbuffer()
		{
			vector<GeometricObjects*> objects;

			RGBColor pixelColor;
			Ray ray;
			//open_window();
			tracer_ptr->UseAccelerationStructure();
			ray.d = Point3D(0, 0, -4);
			Pixel *currentPixel = m_currentPixel;
			//currentPixel += vp.vres * vp.hres - 1;
			float zw = 10;
			int count = 0;
			clock_t startingTime = clock();

			float invWidth = 1 / float(vp.hres), invHeight = 1 / float(vp.vres);
			float fov = 55, aspectratio = 1;
			//float fov = 30, aspectratio = vp.hres / float(vp.vres);

			float angle = tan(PI_NUM * 0.5 * fov / 180.);
			for (unsigned y = m_currentY; y < vp.vres; ++y) {
				for (unsigned x = 0; x < vp.hres; ++x) {

					float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
					float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
					Point3D raydir(xx, yy, -1);
					raydir.normalize();

					Point3D tempDir = m_camera->GetCameraDirection(raydir);

					pixelColor = RGBColor(0);
					ShadeRec rec;
					if (tracer_ptr->TraceRay(Ray(m_camera->eye, tempDir), rec))
					{
						int depth = 0;
						//pixelColor = GetShade(rec, depth);
						float tempTmin = min(rec.tmin * 0.1, 1);
						pixelColor = RGBColor(tempTmin);
						currentImage->SetPixel((int)x, m_currentY, pixelColor);
					}
					//*currentPixel = pixelColor.toInt();
					pixelColor.Convert();
					SetColor(x, y, pixelColor.r, pixelColor.g, pixelColor.b);
					++currentPixel;
					++count;
					m_currentShadrec = rec;
				}

				if (count >= UPDATE_COUNT)
				{
					m_currentY = ++y;
					m_currentPixel = currentPixel;
					return false;
				}
			}

			clock_t endTimer = clock();


			double timeInSeconds = ((double)endTimer - startingTime) / (double)CLOCKS_PER_SEC;
			return true;
		}


		/*
		
			For Testing purposes only

		*/

		void TestRenderSceneTriangles()
		{
			
			vector<GeometricObjects*> allTrinagles = ObjLoader().ParseObjFile("C:\\Users\\Adrian\\Desktop\\bunny.obj.txt", Point3D(50, 50, -25));
			vector<GeometricObjects*> intersectionTriangles;
			RGBColor pixelColor;

			Ray ray;
			//open_window();
			ray.d = Point3D(0, 0, -1);
			Pixel *currentPixel = buffer;
			float zw = 100;

			for (int r = 0; r < vp.hres; r++)
			for (int c = 0; c < vp.vres; c++)
			{
				float x, y;
				pixelColor = RGBColor(0);
				x = vp.s * (c - 0.5 * (vp.hres - 1.0));
				y = vp.s * (r - 0.5 * (vp.vres - 1.0));

				ray.o = Point3D(x, y, zw);
				ShadeRec rec;

				bool intersectionFlag = false;

				double tmin = INT_MAX;

				for (int trinagleIndex = 0; trinagleIndex < allTrinagles.size(); trinagleIndex++)
				{
					
					bool testIntersection = allTrinagles[trinagleIndex]->hit(ray.o, ray.d, tmin, rec);
					if (!intersectionFlag && testIntersection)
						intersectionFlag = true;
				}
				
				if (intersectionFlag)
				{
					int depth = 0;
					pixelColor = GetShade(rec, depth);
					intersectionTriangles.push_back(rec.object);
				}
				else
				{
					intersectionTriangles.push_back(NULL);
				}

				//TODO test this
			}

			for (int r = 0; r < vp.hres; r++)
			for (int c = 0; c < vp.vres; c++)
			{
				float x, y;
				pixelColor = RGBColor(0);
				x = vp.s * (c - 0.5 * (vp.hres - 1.0));
				y = vp.s * (r - 0.5 * (vp.vres - 1.0));

				ray.o = Point3D(x, y, zw);
				ShadeRec rec;
				if (tracer_ptr->TraceRay(ray, rec))
				{
					int depth = 0;
					pixelColor = GetShade(rec, depth);
				}

				*currentPixel = pixelColor.toInt();
				//display_pixels(x, y, pixelColor);
				++currentPixel;
				//TODO test this
			}

		}


		bool TestRenderScene(void)
		{
			vector<GeometricObjects*> objects;

			RGBColor pixelColor;
			Ray ray;
			//open_window();
			ray.d = Point3D(0, 0, -1);
			Pixel *currentPixel = buffer;
			float zw = 100;

			tracer_ptr->DontUseAccelerationSturcture();
			for (int r = 0; r < vp.hres; r++)
				for (int c = 0; c < vp.vres; c++)
				{
					float x, y;
					pixelColor = RGBColor(0);
					x = vp.s * (c - 0.5 * (vp.hres - 1.0));
					y = vp.s * (r - 0.5 * (vp.vres - 1.0));

					ray.o = Point3D(x, y, zw);
					ShadeRec rec;
					if (tracer_ptr->TraceRay(ray, rec))
					{
						int depth = 0;
						pixelColor = GetShade(rec, depth);
						objects.push_back(rec.object);
					}
					else
					{
						objects.push_back(NULL);
					}

					//TODO test this
				}


			tracer_ptr->UseAccelerationStructure();
			int index = 0;

			for (int r = 0; r < vp.hres; r++)
				for (int c = 0; c < vp.vres; c++)
				{
					float x, y;
					pixelColor = RGBColor(0);
					x = vp.s * (c - 0.5 * (vp.hres - 1.0));
					y = vp.s * (r - 0.5 * (vp.vres - 1.0));

					ray.o = Point3D(x, y, zw);
					ShadeRec rec;
					if (tracer_ptr->TraceRay(ray, rec))
					{
						int depth = 0;
						pixelColor = GetShade(rec,depth);
					}

					if (rec.object != objects[index])
					{
						Engine::BoundingBox  *bbox = objects[index]->GetBoundingBox();

						GeometricObjects * currentTestObject = objects[index];
						double tmin, tmax;
						bool intersectionResult = bbox->CheckIntersection(ray, tmin, tmax);
						Point3D point = ray.o + ray.d * tmin;
						std::cout << intersectionResult << std::endl;
					}

					index++;

					*currentPixel = pixelColor.toInt();
					//display_pixels(x, y, pixelColor);
					++currentPixel;
					//TODO test this
				}

			return true;
		}

		


		void display_pixels(const int row, const int column, const RGBColor& raw_color)const
		{

			//what is it?

			//if (vp.show_out_of_gamut)
			//	mapped_color = ClampToColor(raw_color);
			//else
			//	mapped_color = MaxToOne(raw_color);

			//if (vp.gamma != 1.0)
			//	//mapped_color = mapped_color.powc(vp.inv_gamma);

			//	//have to start from max y coordinate to convert to screen coordinates
			//	int x = column;
			//int y = vp.vres - row - 1;
			//bind to the test app
			paintArea->SetPixel(raw_color, row, column);
		}

		RGBColor ClampToColor(const RGBColor &rawColor) const
		{
			RGBColor c(rawColor);

			if (rawColor.r > 1.0 || rawColor.g > 1.0 || rawColor.b > 1.0) {
				c.r = 1.0; c.g = 0.0; c.b = 0.0;
			}

			return (c);
		}

		RGBColor MaxToOne(const RGBColor& c) const
		{
			float max_value = max(c.r, max(c.g, c.b));

			if (max_value > 1.0)
				return (c / max_value);
			else
				return (c);
		}

		Pixel* GetBuffer()
		{
			return buffer;
		}

		private:
			ShadeRec m_currentShadrec;
#pragma region notusedfunctions

			//void RenderPerspectiveView() const
			//{
			//	RGBColor pixelColor;
			//	Ray ray;
			//	//open_window();
			//	ray.o = Point3D(0, 0, eye);

			//	for (int r = 0; r < vp.hres; r++)
			//	for (int c = 0; c < vp.vres; c++)
			//	{
			//		float x, y;

			//		x = vp.s * (c - 0.5 * (vp.hres - 1.0));
			//		y = vp.s * (r - 0.5 * (vp.vres - 1.0));

			//		ray.d = Point3D(x, y, -eye);

			//		pixelColor = tracer_ptr->TraceRay(ray);
			//		display_pixels(x, y, pixelColor);
			//	}
			//}

			//void RenderNonPerspective() const
			//{
			//	RGBColor pixelColor;
			//	Ray ray;
			//	double zw = 100.0;
			//	double xx, yy;

			//	//open_window(vp.hres, vp.vres);
			//	ray.d = Point3D(0, 0, -1);

			//	for (int r = 0; r < vp.vres; r++)
			//	{
			//		for (int c = 0; c <= vp.hres; c++)
			//		{
			//		#ifndef ANTI_ALIASING
			//			x = vp.s * (c - 0.5 * (vp.hres - 1.0));
			//			y = vp.s * (r - 0.5 * (vp.vres - 1.0));

			//			pixelColor = tracer_ptr->TraceRay(Point3D(x, y, zw), Point3D(0, 0, -1));
			//			display_pixels(r, c, pixelColor);
			//		#else
			//			for (int x = 0; x < ANTI_ALIASING_CONSTANT; x++)
			//			{
			//				for (int y = 0; y < ANTI_ALIASING_CONSTANT; y++)
			//				{
			//					xx = vp.s * (c - 0.5 * vp.hres + (x *0.5) / ANTI_ALIASING_CONSTANT);
			//					yy = vp.s * (r - 0.5 * vp.vres + (y * 0.5) / ANTI_ALIASING_CONSTANT);

			//					pixelColor = pixelColor + tracer_ptr->TraceRay(Point3D(xx, yy, zw), Point3D(0, 0, -1));

			//				}
			//			}
			//			int numOfSamples = ANTI_ALIASING_CONSTANT;
			//			//average color
			//			pixelColor = pixelColor / (numOfSamples * numOfSamples);
			//			//set the color of tehe pixel
			//			display_pixels(r, c, pixelColor);
			//		#endif
			//		}
			//	}
			//}
#pragma endregion 

	};
}
#endif // !WORLD_H
